/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

package org.adroitlogic.axis.logger;

import org.apache.axis.handlers.BasicHandler;
import org.apache.axis.MessageContext;
import org.apache.axis.AxisFault;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.management.*;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Hashtable;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.AbstractCollection;
import java.text.SimpleDateFormat;
import java.lang.management.ManagementFactory;

/**
 * A simple logging handler that can be managed via JMX. When enabled, will log requests and
 * responses to a selected path and data format
 *
 * Log requests to <rootPath>/<date>[/<identifier>]/<operation>_HH_MM_SS_<seq>_req.xml
 * e.g. /var/logfiles/2008-09-22/SimpleStockQuoteService/getQuote_12_33_23_45_req.xml
 *  and /var/logfiles/2008-09-22/SimpleStockQuoteService/getQuote_12_33_23_45_res.xml
 *
 * Note that the response file name and location will be determined according to its corresponding
 * request log file
 *
 * @author Asankha C. Perera (asankha AT adroitlogic DOT com)
 */
public class Logger extends BasicHandler implements LoggerMBean {

    private static final Log log = LogFactory.getLog(Logger.class);
    private static final String LOGGER_PATH_PREFIX = "org.adroitlogic.axis.logger.path";
    private static final String UND = "_";

    /**
     * Is logging enabled ?
     */
    private volatile boolean enabled = true;
    /**
     * Where should log files be written?
     */
    private String rootPath;
    /**
     * An optional identifier to differentiate log files
     */
    private String identifier;
    /**
     * Date format to be used to create the directories
     */
    private String dateFormat = "yyyy_MM_dd";

    /**
     * The precomputed path for the current day
     */
    private String computedLogPath;
    /**
     * Should JMX management be allowed?
     */
    private boolean enableJMX = false;

    private volatile long tommorowStartTime;
    private long sequence;

    public void onFault(MessageContext mc) {
        try {
            logMessage(mc);
        } catch (AxisFault e) {
            log.warn("Error logging fault message", e);
        }
    }

    public void invoke(MessageContext mc) throws AxisFault {
        logMessage(mc);
    }

    /**
     * Log a request/response
     * @param mc
     * @throws AxisFault
     */
    public void logMessage(MessageContext mc) throws AxisFault {

        // check if logging is disabled?
        if (!enabled) {
            return;
        }

        // for a response, this would already be set!
        String loggerPathPrefix = (String) mc.getProperty(LOGGER_PATH_PREFIX);

        // for a reqeust message
        if (mc.getResponseMessage() == null) {

            long now = System.currentTimeMillis();
            // check if the date changed
            if (now > tommorowStartTime) {
                recomputeLogPath();
            }

            StringBuffer fileNamePrefix = new StringBuffer(40);
            fileNamePrefix.append(computedLogPath);

            if (mc.getOperation().getName() != null) {
                fileNamePrefix.append(mc.getOperation().getName());
                fileNamePrefix.append(UND);
            }

            fileNamePrefix.append(Calendar.getInstance().get(Calendar.HOUR_OF_DAY));
            fileNamePrefix.append(UND);
            fileNamePrefix.append(Calendar.getInstance().get(Calendar.MINUTE));
            fileNamePrefix.append(UND);
            fileNamePrefix.append(Calendar.getInstance().get(Calendar.SECOND));
            fileNamePrefix.append(UND);
            fileNamePrefix.append(getNextSequenceNo());

            // save path prefix for response
            loggerPathPrefix = fileNamePrefix.toString();
            mc.setProperty(LOGGER_PATH_PREFIX, loggerPathPrefix);
            
        } else if (loggerPathPrefix == null) {
            return;
        }

        FileOutputStream fos = null;
        try {
            if (mc.getResponseMessage() != null) {
                File file = new File(loggerPathPrefix + "_res.xml");
                if (!file.getParentFile().exists()) {
                    file.getParentFile().mkdirs();
                }
                file.createNewFile();
                fos = new FileOutputStream(file);
                mc.getResponseMessage().writeTo(fos);
            } else {
                File file = new File(loggerPathPrefix + "_req.xml");
                if (!file.getParentFile().exists()) {
                    file.getParentFile().mkdirs();
                }
                file.createNewFile();
                fos = new FileOutputStream(file);
                mc.getRequestMessage().writeTo(fos);
            }

        } catch (Exception e) {
            log.warn("Error writing message to file : " + loggerPathPrefix +
                (mc.getResponseMessage() != null ? "_res.xml" : "_req.xml") +
                " :: " + e.getMessage(), e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException ignore) {
                }
            }
        }
    }

    /**
     * Read options for configuration
     * @param ht
     */
    public void setOptions(Hashtable ht) {
        if (ht.get("rootPath") != null) {
            rootPath = (String) ht.get("rootPath");
        }
        if (ht.get("identifier") != null) {
            identifier = (String) ht.get("identifier");
        }
        if (ht.get("dateFormat") != null) {
            setDateFormat((String) ht.get("dateFormat"));
        }
        if (ht.get("enableJMX") != null) {
            enableJMX = Boolean.valueOf((String) ht.get("enableJMX")).booleanValue();
        }
        if (ht.get("enabled") != null) {
            enabled = Boolean.valueOf((String) ht.get("enabled")).booleanValue();
        }
    }

    /**
     * Initialize handler, and bind with JMX
     */
    public void init() {
        super.init();
        if (rootPath != null) {
            recomputeLogPath();

            if (enabled) {
                log.info("AdroitLogic Logger for Apache Axis :: " +
                    "Initialized to root path : " + rootPath);
            }

            if (enableJMX) {
                String mbeanNameString = "org.adroitlogic.axis.logger:Type=Logger";
                if (identifier != null) {
                    mbeanNameString += ",Identifier="+identifier;
                }
                try {
                    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
                    ObjectName mbeanName = ObjectName.getInstance(mbeanNameString);
                    mbs.registerMBean(this, mbeanName);
                } catch (Exception ex) {
                    log.warn("Unable to create object name '" + mbeanNameString
                        + "'; JMX support disabled", ex);
                }
            }
        }
    }

    /**
     * Recompute the logging path for the current day. This path is updated as each new day unfolds
     * @param calendar The Calendar object at init(), and null when updating the log path
     */
    private synchronized void recomputeLogPath() {

        if (tommorowStartTime > System.currentTimeMillis()) {
            return; // already computed by another thread
        }

        Calendar calendar = Calendar.getInstance();
        Calendar tommorow = new GregorianCalendar(
            calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH),
            calendar.get(Calendar.DAY_OF_MONTH) + 1);

        tommorowStartTime = tommorow.getTimeInMillis();

        String strComputedLogPath = rootPath + File.separator +
            new SimpleDateFormat(dateFormat).format(calendar.getTime()) + File.separator;
        if (identifier == null) {
            computedLogPath = strComputedLogPath;
        } else {
            computedLogPath = strComputedLogPath + identifier + File.separator;
        }
    }

    private synchronized String getNextSequenceNo() {
        return Long.toString(++sequence);
    }

    //-- JMX methods --
    public boolean isEnabled() {
        return enabled;
    }

    public String getRootPath() {
        return rootPath;
    }

    public String getIdentifier() {
        return identifier;
    }

    public String getDateFormat() {
        return dateFormat;
    }

    public void disable() {
        if (enabled) {
            enabled = false;
            log.info("AdroitLogic Logger for Apache Axis :: Logging disabled");
        }
    }

    public void enable() {
        if (!enabled) {
            tommorowStartTime = 0;
            recomputeLogPath();
            enabled = true;
            log.info("AdroitLogic Logger for Apache Axis ::" +
                " Logging enabled to root path : " + rootPath);
        }
    }

    public void setDateFormat(String dateFormat) {
        this.dateFormat = dateFormat;
    }

    public void setRootPath(String rootPath) {
        this.rootPath = rootPath;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }
}
